<?php

class Admin_Model_TreeNodeMapper {
	protected $_dbTable;
	static private $_instance = NULL;
	static function getInstance()
	{
		if (self::$_instance == NULL)
			self::$_instance = new Admin_Model_TreeNodeMapper();
		return self::$_instance;
	}
	
	public function setDbTable($dbTable){
        if (is_string($dbTable)) {
            $dbTable = new $dbTable();
        }
        if (!$dbTable instanceof Zend_Db_Table_Abstract) {
            throw new Exception('Invalid table data gateway provided');
        }
        $this->_dbTable = $dbTable;
        return $this;
    }

    public function getDbTable(){
        if (null === $this->_dbTable) {
            $this->setDbTable('Admin_Model_DbTable_TreeNode');
        }
        return $this->_dbTable;
    }

    /**
     * Ищет узел по Id.
     * Возвращает найденный распакованный узел или null, если узел не найден.
     * 
     * @param int $id
     * @return null|TreeNode
     */
    public function find($id,$fetchLayout=false){
    	$t1 = $this->getDbTable();
    	$sql = $t1->select();
    	if ($fetchLayout){
    		$t2 = new Admin_Model_DbTable_Layout();
    		$tableName1 = $t1->info(Zend_Db_Table_Abstract::NAME);
    		$tableName2 = $t2->info(Zend_Db_Table_Abstract::NAME);
    		//$short ? $this->_shortFieldList: 
				//protected $_shortFieldList = array('Title','MetaDescription', 'MetaKeywords', 'HTML','URL','InMenu');
    		$sql->setIntegrityCheck(false)
    			->from(array('t1'=>$tableName1),Zend_Db_Select::SQL_WILDCARD)
    			->join(array('t2'=>$tableName2), 't1.LayoutId=t2.Id','Layout')
    			->where('t1.Id = ?',$id);
    	}
    	else
    		$sql->where('Id = ?',$id);
    	
    	$row = $t1->fetchRow($sql);
		$node = is_null($row)? null : $this->createNode($row->toArray());
    	return $fetchLayout? array($node,is_null($row)? null : $row->Layout) : $node;
    }
	
	public function findByTag($tags){
		$t1 = $this->getDbTable()->select();
		$filter=new Zend_Filter_StringTrim();
		$list1 = explode(',',$tags);
		foreach($list1 as $l)
		{
			$l = $filter->filter($l);
			if ($l=='') continue;
			$query = '';
			if (strpos($l,'+')===false)
			{
				$query = "Tag LIKE '%,".$l.",%'";
			}
			else
			{
				$list2 = explode('+',$l);
				foreach($list2 as $t)
				{
					$t = $filter->filter($t);
					if ($t=='') continue;
					$query .= $query==''?'':' AND ';
					$query .= "(Tag LIKE '%,".$t.",%')";
				}
			}
			$t1->orWhere($query)->order('LeftId');
		}
		//echo $t1->assemble(); //exit;
		return $this->customFind($t1);
	}
	
    /*
     * @param $sql - Zend_Db_Sql object
     * Возаращает массив узлов в распакованном виде
     */
    public function customFind($sql){
    	$set = $this->getDbTable()->fetchAll($sql);
    	$list = array();
    	foreach ($set as $r){
    		$node = is_null($r)? null : $this->createNode($r->toArray());
    		if ($node!=null)
    			$list[] = $node;
    	}
    	return $list;
    }
    
	/**
	 * Ищет индексный узел
	 * Возвращает найденный распакованный узел или null, если узел не найден.
	 * 
	 * @return null|TreeNode
	 */
	public function findIndexPage(){
    	$t1 = $this->getDbTable();
    	$t2 = new Admin_Model_DbTable_Layout();
    	$tableName1 = $t1->info(Zend_Db_Table_Abstract::NAME);
    	$tableName2 = $t2->info(Zend_Db_Table_Abstract::NAME);
    	$row = $t1->fetchRow($t1->select()
    						->setIntegrityCheck(false)
    						->from(array('t1'=>$tableName1))
    						->join(array('t2'=>$tableName2), 't1.LayoutId=t2.Id','Layout')
    						->where("URL='/' AND Level>1")
    							);
    	$node =  is_null($row)? null : $this->createNode($row->toArray());
    	return array($node,is_null($row)? null : $row->Layout);
    }
    
    /**
     * Возвращает массив всех узлов (распакованными).
     * 
     * @return array
     */
    public function fetchAll(){
    	$t = $this->getDbTable();
    	$rowSet = $row = $t->fetchAll(null, "LeftId DESC");
    	$nodes = array();
    	$row = $rowSet->current();
    	while($row!=null){
    		$nodes[] = $this->createNode($row->toArray());
    		$rowSet->next();
    		$row = $rowSet->current();
    	}
    	return $nodes;
    }
    
	/**
	 * Ищет коренной узел (для отображение в дереве)
	 * Возвращает нераспакованный узел с урезанными параметрами или null, если узел не найден.
	 * @param bool $extjs
	 * @return Admin_Model_TreeNode| array в зависимости от параметра $extjs 
	 */
	public function findRoot($extjs = false){
    	$t = $this->getDbTable();
    	$row = $t->fetchRow($t->select()->from($t,array('Id','Name','LeftId','RightId'))->where("ParentId=Id"));
    	if (is_null($row)) return null;
    	if (!$extjs)
    	{
    		$entry = new Admin_Model_TreeNode();
	        $entry->setId($row->Id)->setName($row->Name);
    	}
    	else
    	{
            $entry = array(
                    'id'=>$row->Id,
                    'href'=>'#'.$row->Id,
                    'text'=>$row->Name,
                    'leaf'=>($row->LeftId+1)==$row->RightId,
            );
    	}
    	return $entry;
    }
    
    public function fetchSubTree(Admin_Model_TreeNode $node){
    	$t = $this->getDbTable();
    	$rowset = $t->fetchAll($t->select()	->where("LeftId>?",$node->getLeftId())
    										->where("RightId<?",$node->getRightId())
    										->order('LeftId')
    										);
    	$nodes = array();
    	$row = $rowset->current();
    	while($row!=null){
    		$nodes[] = $this->createNode($row->toArray());
    		$rowset->next();
    		$row = $rowset->current();
    	}
    	return $nodes;
    }
    
	/**
	 * Возвращает массив, содержащей узлы детей первого уровня.
	 * Если $extjs ложь - вес узлы распакованы.
	 * 
	 * @param int $parentId идентификатор родительского узла
	 * @param bool $extjs истина, если вернуть массив для ExtJs, ложь в противном случае 
	 * @return array - массив узлов
	 */
	public function fetchChildrens($parentId, $extjs = false, $asc = true){
    	$t = $this->getDbTable();
    	$sql = $extjs ? $t->select()->from($t,array('Id','Name','LeftId','RightId','URL','Priority','ChangeFreq')): $t->select();
    	$sql->where("ParentId={$parentId} AND ParentId<>Id")
            ->order($asc?'LeftId ASC':'LeftId DESC');
    	$resultSet = $t->fetchAll($sql);
    	$entries   = array();
        foreach ($resultSet as $row) {
        	if ($extjs)
        	{
        		$entry = array(
                    'id'=>$row->Id,
                    'href'=>'#'.$row->Id,
                    'text'=>$row->Name,
                    'leaf'=>($row->LeftId+1)==$row->RightId,
                );
        	}
        	else
        	{
        		$entry = $this->createNode($row->toArray()); //new Application_Model_TreeNode($row->toArray());
        	}
            $entries[] = $entry;
        }
        return $entries;
    }
    
    public function fetchByCfgId($CfgId, $asc = true){
    	$resultSet = $this->getDbTable()->fetchAll(
                "CfgId=".$CfgId, 
                $asc?'LeftId ASC':'LeftId DESC'
                );
    	$entries   = array();
        foreach ($resultSet as $row) {
        	$entry = $this->createNode($row->toArray());
            $entries[] = $entry;
        }
        return $entries;
    }
    
    public function fetchSibling($node){
    	if (!($node instanceof Admin_Model_TreeNode)) throw new Exception("Узел не является наследником Admin_Model_TreeNode", 0);
    	if (null == $node->getParentId())
    		throw new Exception("Не установлен родительский узел", 0);
        $sql = $this->getDbTable()->select()
                ->where("ParentId=".$node->getParentId())
                ->order('LeftId');
    	$resultSet = $this->getDbTable()->fetchAll($sql);
    	$entries   = array();
        foreach ($resultSet as $row) {
        	$entry = $this->createNode($row->toArray());
            $entries[] = $entry;
        }
        return $entries;
    }
    
    protected function _sqlTopMenu(){
    	$t = $this->getDbTable();
    	$sql= $t	->select()
	    			->from ( $t, Zend_Db_Select::SQL_WILDCARD )
	    			->where ( "(InMenu=1) AND (Level=(SELECT MIN(Level) FROM ".$t->info(Zend_Db_Table_Abstract::NAME)." WHERE InMenu=1))")
	    			->order ( "LeftId" );
	    return $sql;
    }
    
    /**
     * Возвращает самое верхнее меню
     * @param int $id идентификатор текущего узла
     * @param string $UIClass класс для текуего элемента меню
     * @return array вложенный массив со страницами
     */
    public function fetchTopMenu($id,$UIClass){
    	$rowSet = $this->getDbTable()->fetchAll ( $this->_sqlTopMenu() );
		return $this->_parseMenu ($id,$UIClass,$rowSet);
    }
    /**
     * Возвращает путь до текущего узла заданного в $node. Возвращает вложенные страницы в виде массива
     * @param Admin_Model_TreeNode $node текущий узел
     * @return array
     */
	public function fetchBreadcrumbs(Admin_Model_TreeNode $node){
		$sql = $this->getDbTable()
			->select()
			->where("Level<=:Level AND LeftId<=:LeftId AND RightId>=:RightId")
			->order ( "LeftId" )
			->bind ( array (
						'Level' => $node->getLevel(), 
						'RightId' => $node->getRightId(), 
						'LeftId' => $node->getLeftId()
			    	));
		$rowSet = $this->getDbTable()->fetchAll ( $sql );
		return $this->_parseMenu ($node->getId(),"",$rowSet);
	}
    
    public function findMenuCustom($sql, $id=-1, $UIClass='', $createNode=false){
        return $this->_parseMenu($id,$UIClass,$this->getDbTable()->fetchAll($sql), -1, $createNode);
    }
    
	public function findMenu(Admin_Model_TreeNode $node=null,$id=-1,$UIClass=''){
    	$t = $this->getDbTable();
    	$sql = $node!=null? $t->select()
			    			->from ( $t, Zend_Db_Select::SQL_WILDCARD )  //  
			    			//->where ( "(((Level<:Level) AND ((LeftId<:LeftId) AND (RightId>:RightId))) OR (ParentId=:ParentId) OR (ParentId=:Id) OR (Level=3)       ) AND (InMenu=1)" )
			    			->where ("InMenu=1")
			    			->order ( "LeftId" )
			    			->bind ( array (
			    				'Id' => $node->getId(), 
			    				'Level' => $node->getLevel(), 
			    				'RightId' => $node->getRightId(), 
			    				'LeftId' => $node->getLeftId() 
			    				,'ParentId' => $node->getParentId() 
			    			))
    			:$t->select()
			    			->from ( $t, Zend_Db_Select::SQL_WILDCARD )
			    			->where ( "(InMenu=1) AND (Level=(SELECT MIN(Level) FROM ".$t->info(Zend_Db_Table_Abstract::NAME)." WHERE InMenu=1))")
			    			//(((Level<:Level) AND ((LeftId<:LeftId) OR (RightId>:RightId))) OR (ParentId=:Id) OR (ParentId=:ParentId)) AND (InMenu=1)" )
			    			->order ( "LeftId" );
		//echo $sql->assemble();
    	$rowSet = $t->fetchAll ( $sql );
    	//echo "id=".$id;
		return $this->_parseMenu ($id,$UIClass,$rowSet);
    }
    
    public function fetchFullMenu($id=-1,$UIClass=''){
    	$t = $this->getDbTable();
    	$rowSet = $t->fetchAll ( $t->select()->where('InMenu=1')->order ( "LeftId" ));
    	return $this->_parseMenu ($id,$UIClass,$rowSet);
    }
    
    public function fetchMenuCatHierarchy1(Admin_Model_TreeNode $node=null,$UIClass='',$catId){
    	$t = $this->getDbTable();
    	$info = $t->info();
        $tableName = $info[Zend_Db_Table_Abstract::NAME];
            
    	$sql = $node!=null? $t->select()
			    			->from (array('p'=>$tableName), "")
			    			->joinInner(array('r'=>$tableName),
			    				"(r.LeftId>p.LeftId AND r.RightId<p.RightId) OR (r.ParentId=p.ParentId)", 
			    				Zend_Db_Select::SQL_WILDCARD
			    			)
			    			->where ("r.InMenu=1 AND p.ParentId=:catId AND p.LeftId<=:LeftId AND p.RightId>=:RightId /*AND (r.Level<:Level OR (r.ParentId=:ParentId) OR (r.ParentId=:Id))*/")
			    			->order ("LeftId")
			    			->bind  ( array (
			    				'catId'		=> $catId, 
			    				'Id'		=> $node->getId(),
			    				'RightId'	=> $node->getRightId(), 
			    				'LeftId'	=> $node->getLeftId(),
			    				'Level'		=> $node->getLevel(),
			    				'ParentId'	=> $node->getParentId(),
			    			))
    			:$t->select()
			    			->from ( $t, Zend_Db_Select::SQL_WILDCARD )
			    			->where ( "InMenu=1 AND ParentId=:catId")
			    			->order ( "LeftId" )
			    			->bind  ( array (
			    				'catId'		=> $catId	
			    			));
		//echo $sql->assemble();
    	$rowSet = $t->fetchAll ( $sql );
    	//echo "id=".$id;
		return $this->_parseMenu (
			$node!=null?$node->getId():-1,
			$UIClass,
			$rowSet);
    }
    
	public function fetchMenuCatHierarchy(Admin_Model_TreeNode $node=null,$UIClass='',$catId){
    	$t = $this->getDbTable();
    	$info = $t->info();
        $tableName = $info[Zend_Db_Table_Abstract::NAME];
            
    	$sql = $node!=null? $t->select()
    						->distinct()
			    			->from (array('b'=>$tableName), "")
			    			->joinInner(array('r'=>$tableName),
			    				"b.ParentId=r.ParentId", 
			    				Zend_Db_Select::SQL_WILDCARD
			    			)
			    			->where ("b.InMenu=1 AND ((b.LeftId<=:LeftId AND b.RightId>=:RightId) OR (b.LeftId>:LeftId AND b.RightId<:RightId AND b.Level=:Level+1))")
			    			->order ("LeftId")
			    			->bind  ( array (
			    				//'catId'		=> $catId, 
			    				//'Id'		=> $node->getId(),
			    				'RightId'	=> $node->getRightId(), 
			    				'LeftId'	=> $node->getLeftId(),
			    				'Level'		=> $node->getLevel(),
			    				//'ParentId'	=> $node->getParentId(),
			    			))
    			:$t->select()
			    			->from ( $t, Zend_Db_Select::SQL_WILDCARD )
			    			->where ( "InMenu=1 AND ParentId=:catId")
			    			->order ( "LeftId" )
			    			->bind  ( array (
			    				'catId'		=> $catId	
			    			));
		//echo $sql->assemble();
    	$rowSet = $t->fetchAll ( $sql );
    	//echo "id=".$id;
		return $this->_parseMenu (
			$node!=null?$node->getId():-1,
			$UIClass,
			$rowSet);
    }
    
 	/**
 	 * Конвертирует упорядоченную по LeftId выборку в массив пригодный для использования в Zend_Navigation  
	 * Узлы могут быть предварительно распакованы (десериализованы) - это нужно для конфигурации Shortcut (Ярлык).
	 * Узлы могут быть принудительно распакованы и добавлены в свойства страницы (Node в ассоциативном массиве свойств страницы в Navigation) 
	 * если параметр $createNode задан в true.
 	 * @param int $currentPageId идентификатор текущей страницы
 	 * @param string $UIClass css класс задаваемый для элемента меню, являющегося текущим 
 	 * @param Rowset $rowSet выборка из БД
 	 * @param int $pId служит для итерации, должен быть -1
	 * @param bool $createNode если истина, то в страницу будет добавлено поле Node с созданным узлом
 	 * @return array иерархический массив страниц
 	 */
 	protected function _parseMenu($currentPageId,$UIClass,$rowSet,$pId = -1, $createNode=false){
    	$result = array();
    	$row = $rowSet->current();
    	while ($row!=null)
    	{
    		if (($pId==-1) || ($pId==$row->ParentId))
    		{
				if ($createNode || (substr_compare ( 'O:20:"Admin_Model_Shortcut"' , $row->XML , 0, 27)==0)){
					$n = $this->createNode($row->toArray());
					$row->MenuTitle = $n->getMenuTitle();
					$row->Title = $n->getTitle();
					$row->URL = $n->getURL();
				}
				else $n = null;
    			$page = array(
					'ParentId' => $row->ParentId,
					'LeftId' => $row->LeftId,
					'RightId' => $row->RightId,
                    'CfgId' => $row->CfgId,
                    'NodeId'=>$row->Id,
	    			'label'	=>	$row->MenuTitle,
					'title'=>$row->Title,
	    			'uri'	=> $row->URL,
					'Node' =>$n
	    		);
                if ($n instanceof Admin_Model_Shortcut)
                    $page['RefId'] = $n->getNode()->getId();
	    		if (($currentPageId>-1) && ($row->Id==$currentPageId))
				{
					$page['active'] = true;
					if (!empty($UIClass))
						$page['class'] = $UIClass;
				}
	    			
	    		$rowSet->next();
	    		$nextRow = $rowSet->current();
	    		if ((null!=$nextRow) && ($nextRow->ParentId==$row->Id))
		    	{
		    		$page['pages'] = $this->_parseMenu($currentPageId,$UIClass,$rowSet,$row->Id);
		    		$row = $rowSet->current();
		    	}
		    	else
		    		$row = $nextRow;
    		}
    		else
    			break;
    		$result[] = $page;
    	}
		return $result;
    }
    
    protected function _parseMenuOld($activePageId,$rowSet){
    	$id = 0;
    	$result = array();
    	$row = $rowSet->current();
    	while ($row!=null)
    	{
    		$page = array(
	    			'label'	=>	$row->Name,
	    			'uri'	=> $row->URL//=='/'  ? '/' : $row->URL.'/',
	    			//'active' => $row->Id==$activePageId,
    				//'visible'=> $row->InMenu==1
	    	);
	    	//if ($row->Id==$activePageId) $page['active'] = true;
	    	$rowSet->next();
	    	$nextRow = $rowSet->current();
	    	if ((null!=$nextRow) && ($nextRow->ParentId==$row->Id))
	    	{
	    		$page['pages'] = $this->_parseMenu($activePageId,$rowSet);
	    		$rowSet->next();
	    		$row = $rowSet->current();
	    	}
	    	else
	    		$row = $nextRow;
	    	$result[] = $page;
    	}
		return $result;
    }

    /**
     * Только сохраняет узел
     * @param Admin_Model_TreeNode $node	сохраняемый узел
     * @param bool|array $onlyFields		если истина - сораняются только поля заданные в Admin_Model_TreeNode,
     * 											ложь - все поля получаемые из типа, к которому принадлежит $node,
     * 										если массив - поля из массива 
     */
    public function save(Admin_Model_TreeNode $node,$onlyFields=false){
        $data = $node->toArray($onlyFields);
        if (null === ($id = $node->getId())) {
            throw new Zend_Exception("'Id' must be specified in Admin_Model_TreeNode before calling 'save' method");
        }
        else {
			$data['LastMod'] = date('Y-m-d');
            $this->getDbTable()->update($data, array('id = ?' => $id));
            $this->clearCache($id);
        }
    }
    
    /**
     * Добавляет подузел
     * @param int|Admin_Model_TreeNode $parent	- родительский узел
     * @param Admin_Model_TreeNode $newNode		- добавляемый узел
     * @param bool|array $onlyFields			если истина - сохраняются только поля заданные в Admin_Model_TreeNode,
     * 												 ложь - все поля получаемые из типа, к которому принадлежит $node,
     * 											если массив - поля из массива
     */
    public function addSubNode($parent, Admin_Model_TreeNode $newNode){
    	$parentNode = ($parent instanceof Admin_Model_TreeNode)? $parent : $this->find($parent);    	
    	$newNode->setLeftId($parentNode->getRightId())
				->setRightId($parentNode->getRightId()+1)
				->setLevel($parentNode->getLevel()+1)
				->setParentId($parentNode->getId());
				
    	$t = $this->getDbTable();
    	$db = $t->getAdapter();
    	$newId = 0;
    	try
    	{
    		//TODO: Check URL existence? but skip achros like '^#dsfsdf$'
    		$db->beginTransaction();
	    	$t->update(array(
	    		'RightId'=>new Zend_Db_Expr('RightId+2'),
	    		'LeftId'=>new Zend_Db_Expr("IF(LeftId > {$parentNode->RightId}, LeftId + 2, LeftId)")
	    		),
	    		"RightId>={$parentNode->RightId}"
	    	);
	    	$parentNode->setRightId($parentNode->getRightId()+2);
			
	    	$data = $newNode->toArray(true);
	    	unset($data['Id']);
	    	$newId = $t->insert($data);
	    	$db->commit();
	    	$newNode->setId($newId);
    	}
    	catch(Exception $e)
    	{
    		$db->rollBack();
    		throw $e; 
    	}
    }
    

    /**
     * Удаляет узел
     * @param int $Id идентификатор узла
     * @return int - кол-во удаленных узлов.
     */
    public function delete($Id){
    	$node = $this->find($Id);
    	if ($node->getId()==$node->getParentId())
    		return 0;
    	$t = $this->getDbTable();
    	$db = $t->getAdapter();
    	try
    	{
    		$db->beginTransaction();
    		$num = $t->delete("LeftId >= {$node->getLeftId()} AND  RightId <= {$node->getRightId()}");
    		//DELETE FROM my_tree WHERE left_key >= $left_key AND  right_ key <= $right_key 
    		$a = $node->getRightId()-$node->getLeftId()+1;
    		$b = $node->getRightId()-$node->getLeftId()+1;
    		$t->update(
    			array(
    				'LeftId'=>new Zend_Db_Expr("IF(LeftId > {$node->getLeftId()}, LeftId-{$a}, LeftId)"),
    				'RightId'=>new Zend_Db_Expr("RightId-{$b}") 
    			),
    			"RightId > {$node->getRightId()}"
    		);
    		//UPDATE my_tree SET left_key = IF(left_key > $left_key, left_key – ($right_key - $left_key  + 1), left_key), right_key = right_key – ($right_key - $left_key + 1) WHERE right_key > $right_key 
    		$db->commit();
    	}
    	catch(Exception $e)
    	{
    		$db->rollBack();
    		$num = 0;
    		throw $e; 
    	}
    	return $num;
    }

    public function fetchURLs($all = false){
	 	$t = $this->getDbTable();
		$sql = $t->select()
			->from($t,array('Id','URL','LastMod','ChangeFreq','Priority'))
			->order("Priority DESC");
			
		if ($all)
			$sql->where("(ParentId<>Id)");
		else
			$sql->where("(ParentId<>Id) AND (URL<>'/')");
    	$rowSet = $t->fetchAll($sql);
    	$entries = array();
        foreach ($rowSet as $row) {
        	$entries[] = array(
				'Id'=>$row->Id,
				'URL'=>$row->URL, 
				'LastMod'=>$row->LastMod,
				'ChangeFreq'=>$row->ChangeFreq,
				'Priority'=>$row->Priority
			);
        }
    	return $entries;
    }
    
    public function fetchParent($childId){
    	$t = $this->getDbTable();
    	$tableName = $t->info(Zend_Db_Table_Abstract::NAME);
    	
    	$row = $t->fetchRow($t->select()
    							->from(array('t1'=>$tableName),null)
    							->join(array('t2'=>$tableName), 't1.ParentId=t2.Id')
    							->where('t1.Id=?',$childId)
    							);
    							
    	return null!=$row? $this->createNode($row->toArray()) : null;
    }
    
    public function _createNode($row){
        echo "WARNING! Use createNode() instead";
        return $this->createNode($row);
    }
    /*
     * Возвращает распакованный объект.
     * Чтоб обяхект распаковался в ассоциативном массиве $row должно быть поле XML
     * @param @row array строка-выбранная из БД
     */
    public function createNode($row){
    	if (!is_array($row)) throw new Zend_Exception('$row must be an array');
    	$result = null;
        if (empty($row['XML']))
            $result = new Admin_Model_TreeNode($row);
        else{
            $result = unserialize($row['XML']);
            if ($result instanceof Admin_Model_TreeNode){
                $result->setOptions($row);
            }
        }
    	return $result;
    }
    
    /**
     * Добавляет дерево узлов к заданному узлу
     * @param Admin_Model_TreeNode | int $newParent - идентификатор или сам узел, который будет родительским
     * @param array $nodes - массив с узлами типа Admin_Model_TreeNode, которые задают дерево 
     * @return false | Admin_Model_TreeNode - либо выход из процедуры, либо узел на котором остановилас рекурсия
     */
    protected function _insertSubTree($newParent,&$nodes){
    	//echo 'IN[ ';
    	$node1 = current($nodes);
    	$node2 = false;
    	while($node1!==false){
    		$node = clone $node1;
            $node->setXML($node->serialize());
    		/*if (method_exists($node,'render'))
			{
				$node->setHTML	($node->render());
	    				
			}
			else
			{
				$node->pack();
			}*/
			
			
			$this->addSubNode($newParent,$node);
			//echo $node->getName().' R='.$node->getRightId().' ';
			
			$node2 = next($nodes);
			if (($node2!=false) && ($node2->getRightId()<$node1->getRightId()))
			{
				$node2 = $this->_insertSubTree($node,$nodes); //это ребенок, рекурсия
				/*if ($newParent instanceof Admin_Model_TreeNode)
					$newParent = $newParent->getId();*/
			}
				
			if (($node2==false) || ($node2->getLevel()==$node1->getLevel()))
				$node1 = $node2; //это либо конец, либо брат
			else
				$node1 = false;	//это родитель, но не наш
    	}
    	//echo ']';
    	//self::$yyy++;
    	//if (self::$yyy==2) exit;
    	return $node2;
    }
	
	/**
	 * Копирует элемент узла (и опционально, все его дерево) в заданный узел
	 * @param int $id - идентификатор копируемого элемента
	 * @param int $destId - ижентификатор элемента приемника
	 * @param bool $withSubTree - копировать ли дерево или узел. true- дерево.
	 */
	public function copyNode($id,$destId,$withSubTree=false){
		$node = $this->find($id);
		if ($withSubTree)
		{
			$nodes = $this->fetchSubTree($node);
			array_unshift($nodes,$node);
		}
		else
			$nodes = array($node);
		$this->_insertSubTree($destId,$nodes);
    }
    
    /**
     * Перемещает элемент с его поддеревом в заданный родительский узел
     * Данный метод так же реализует изменение порядка следования узлов.
     * @param int $id - идентификатор перемещаемого элемента
     * @param int $destId - идентификатор узла приемника (в случае если $moveType==0) или идентификатор узла, за которым будет вставлен перемещаемый узел  
     * @param int $moveType - тип вставки (в лемент | после элемента) 
     */
    public function moveNode($id,$destId,$moveType=0){
    	$node = $this->find($id);
    	if ($node->getParentId()==$destId)
    		throw new Exception('Перемещаемый узел уже находится в узле назначения');
    	$level = $node->getLevel(); //2
    	$left_key = $node->getLeftId(); //1500
    	$right_key = $node->getRightId(); //1501
    	//echo 'moveType='.$moveType.'; ';
    	$destNode = $this->find($destId);
    	$parentId = 0==$moveType ? $destId : $destNode->getParentId(); //echo 'parentId='.$parentId.' '; 
    	$level_up = 0==$moveType ? $destNode->getLevel() : $destNode->getLevel()-1; //echo 'level_up='.$level_up.' ';//1
    	$right_key_near = 0==$moveType ? $destNode->getRightId()-1 : $destNode->getRightId(); //echo 'right_key_near='.$right_key_near.'; ';//1499 
    	
    	
    	$skew_level = $level_up - $level + 1; //echo 'skew_level='.$skew_level.' ';//0
    	$skew_tree = $right_key - $left_key + 1; //echo 'skew_tree='.$skew_tree.' ';//2
    	
    	//$subtree = $this->fetchSubTree($node);
    	
    	//update
    	$t = $this->getDbTable();
    	if ($right_key_near<$right_key)
    	{
    		//echo "1_";
    		//узел перемещается в облась вышестоящих узлов
    		$skew_edit=$right_key_near - $left_key + 1; //echo 'skew_edit='.$skew_edit.' ';//0
    		//exit;
    		$r = $t->update(array(
    						'RightId'	=>new Zend_Db_Expr("IF(LeftId>={$left_key}, RightId+{$skew_edit}, IF(RightId<{$left_key}, RightId+{$skew_tree}, RightId))"),
    						'Level'		=>new Zend_Db_Expr("IF(LeftId>={$left_key}, Level+{$skew_level}, Level)"),
    						'LeftId'	=>new Zend_Db_Expr("IF(LeftId>={$left_key}, LeftId+{$skew_edit}, IF(LeftId>{$right_key_near}, LeftId+{$skew_tree}, LeftId))")    		
    					),
    					"RightId>{$right_key_near} AND LeftId<{$right_key}");
    	}
    	else {
    		//echo "2_";
    		//нижестоящих
    		$skew_edit = $right_key_near - $left_key + 1 - $skew_tree; //echo 'skew_edit='.$skew_edit.' ';
    		//exit;
    		$r = $t->update(array(
    						'LeftId'	=>new Zend_Db_Expr("IF(RightId<={$right_key}, LeftId+{$skew_edit}, IF(LeftId>{$right_key}, LeftId-{$skew_tree}, LeftId))"),
    						'Level'		=>new Zend_Db_Expr("IF(RightId<={$right_key}, Level+{$skew_level}, Level)"),
    						'RightId'	=>new Zend_Db_Expr("IF(RightId<={$right_key}, RightId+{$skew_edit}, IF(RightId<={$right_key_near}, RightId-$skew_tree, RightId))"),
    					),
    					"RightId>{$left_key} AND LeftId<={$right_key_near}"
    		);
    	}
    	//echo $r;
    	$t->update(array('ParentId'=>$parentId),"Id={$id}");
    }
    
	public function clearCache($id=null){
		$list = Zend_Controller_Front::getInstance()->getParam('bootstrap') 
				->getResource('cachemanager')->getCaches();
		if ($list!=null)
			foreach($list as $cache){
				if ($id!=null){
                    $cache->remove((string)$id);
					$cache->remove('node_'.$id);
                    $cache->remove('render_'.$id);
                }
				else
					$cache->clean();
			}
	}
}

?>